/**
 * The Free Gaming System; player data handling functions.
 *
 * Copyright 2006 the Free Gaming System developers.
 *
 * FGS is the legal property of its developers, whose names are too
 * numerous to list here.  Please refer to the COPYRIGHT file
 * distributed with this source distribution.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA
 */

#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#include "fgs_game.h"
#include "fgs_internal.h"

static fgs_error_t
validate_magic(fgs__socket_reply_t *reply)
{
  if (strcmp(reply->result, "FGS") != 0)
    {
      fgs__socket_free_reply(reply, FGS_TRUE);
      return FGS_ERR_MALFORMED_MAGIC;
    }

  if (reply->argc != 2 ||
      strcmp(reply->argv[0], "CLIENT") != 0)
    {
      fgs__socket_free_reply(reply, FGS_TRUE);
      return FGS_ERR_MALFORMED_MAGIC;
    }
  if(strcmp(reply->argv[1], FGS_CLIENT_PROTO_VER_STR) != 0)
    {
      fgs__socket_free_reply(reply, FGS_TRUE);
      return FGS_ERR_CLIENT_PROTO_MISMATCH;
    }

  return FGS_OK;
}

static fgs_error_t
validate_auth_reply(fgs__socket_reply_t *reply)
{
  if (strcmp(reply->result, "LOGIN") != 0)
    {
      fgs__socket_free_reply(reply, FGS_TRUE);
      return FGS_ERR_MALFORMED_REPLY;
    }

  if (reply->argc != 2)
    {
      fgs__socket_free_reply(reply, FGS_TRUE);
      return FGS_ERR_MALFORMED_REPLY;
    }

  return FGS_OK;
}


static void
add_player_record(fgs_ctx_t *ctx, fgs_uuid_t uuid, fgs_baton_t conn_baton)
{
  ctx->n_players++;
  ctx->players = fgs__realloc(ctx->players, ctx->n_players,
                              sizeof(*ctx->players));

  ctx->players[ctx->n_players - 1].conn_baton = conn_baton;
  ctx->players[ctx->n_players - 1].uuid = uuid;
}


static void
remove_player_record(fgs_ctx_t *ctx, fgs_uuid_t uuid)
{
  int i;

  for (i = 0; i < ctx->n_players; i++)
    {
      if (strncmp(ctx->players[i].uuid, uuid,
                  strlen(ctx->players[i].uuid)) == 0)
        {
          fgs__free(ctx->players[i].uuid);
          memmove(&ctx->players[i], &ctx->players[ctx->n_players - 1],
                  sizeof(ctx->players[i]));
          ctx->n_players--;
          ctx->players = fgs__realloc(ctx->players, ctx->n_players,
                                      sizeof(*ctx->players));

          return;
        }
    }
}


static char*
get_uuid_from_record(fgs_ctx_t *ctx, fgs_baton_t conn_baton)
{
  int i;

  for (i = 0; i < ctx->n_players; i++)
    if (ctx->cb_table->cmp_client(conn_baton,
                                  ctx->players[i].conn_baton) == FGS_TRUE)
      return fgs__strdup(ctx->players[i].uuid);

  return NULL;
}


static char*
make_nonce(int len)
{
  char nonce_chars[62] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    "abcdefghijklmnopqrstuvwxyz"
    "0123456789";
  char *nonce;
  int i;

  nonce = fgs__alloc(len+1, sizeof(char));

  for (i = 0; i < len; i++)
    {
      int nonce_idx = (int) ((double)(sizeof(nonce_chars)-1) *
                            (rand() / (RAND_MAX + 1.0)));

      nonce[i] = nonce_chars[nonce_idx];
    }

  return nonce;
}


fgs_error_t
fgs_new_player(fgs_ctx_t *ctx, fgs_uuid_t *uuid,
               fgs_baton_t conn_baton, fgs_bool_t bypass_auth)
{
  char buf[4] = {0};
  char *nonce;

  /* Validate parameters */
  if (ctx == NULL || uuid == NULL)
    return FGS_ERR_INVALID_PARAMS;

  /* First look for the FGS magic header. */
  if (!bypass_auth)
    FGS_ERR(ctx->cb_table->read_client(conn_baton, buf, 3, FGS_FALSE));

  /* If the magic header is there, try an authentication sequence to
   * register the player as an FGS user. */
  if (!bypass_auth && strcmp(buf, "FGS") == 0)
    {
      fgs__socket_reply_t *rep;
      char *login, *passwd_hash;
      fgs_error_t err;

      FGS_ERR(fgs__socket_recv(conn_baton, ctx->cb_table->read_client, &rep));
      FGS_ERR(validate_magic(rep));
      FGS_ERR(fgs__socket_free_reply(rep, FGS_TRUE));

      /* Build an authentication request and send it to the client. */
      nonce = make_nonce(16);
      FGS_ERR(fgs__socket_send(conn_baton, ctx->cb_table->write_client,
                               "AUTH", "s", nonce));

      /* Get a reply and validate it. */
      FGS_ERR(fgs__socket_recv(conn_baton, ctx->cb_table->read_client, &rep));
      FGS_ERR(validate_auth_reply(rep));
      login = rep->argv[0];
      passwd_hash = rep->argv[1];
      FGS_ERR(fgs__socket_free_reply(rep, FGS_FALSE));

      err = fgs__proxy_new_client(ctx, uuid, login, passwd_hash, nonce);
      fgs__free(login);
      fgs__free(passwd_hash);
      fgs__free(nonce);

      if (err != FGS_OK && err != FGS_ERR_UNAUTHORIZED_PLAYER)
        return err;
      else if (err == FGS_OK)
        {
          add_player_record(ctx, fgs__strdup(*uuid), conn_baton);
          return FGS_OK;
        }
    }

  /* If the header isn't there, or we have an anonymous player, for which
     we just need an UUID. */
  FGS_ERR(fgs__proxy_new_client(ctx, uuid, NULL, NULL, NULL));
  add_player_record(ctx, fgs__strdup(*uuid), conn_baton);

  return FGS_OK;
}


fgs_error_t
fgs_new_player_tcp(fgs_ctx_t *ctx, fgs_uuid_t *uuid,
                   int conn_fd, fgs_bool_t bypass_auth)
{
  void *conn_baton = (void*)conn_fd;

  return fgs_new_player(ctx, uuid, conn_baton, bypass_auth);
}


fgs_error_t
fgs_remove_player(fgs_ctx_t *ctx, fgs_uuid_t player_uuid)
{
  /* Validate parameters */
  if (ctx == NULL || player_uuid == NULL)
    return FGS_ERR_INVALID_PARAMS;

  remove_player_record(ctx, player_uuid);
  FGS_ERR(fgs__proxy_del_client(ctx, player_uuid));

  /* All match pruning is done proxy-side, so we're done! */
  return FGS_OK;
}


fgs_error_t
fgs_get_player(fgs_ctx_t *ctx, fgs_uuid_t *uuid, fgs_baton_t conn_baton)
{
  /* Validate parameters */
  if (ctx == NULL || uuid == NULL)
    return FGS_ERR_INVALID_PARAMS;

  *uuid = get_uuid_from_record(ctx, conn_baton);

  if (*uuid == NULL)
    return FGS_ERR_INVALID_PLAYER;

  return FGS_OK;
}


fgs_error_t
fgs_get_player_tcp(fgs_ctx_t *ctx, fgs_uuid_t *uuid, int conn_fd)
{
  fgs_baton_t conn_baton = (fgs_baton_t)conn_fd;

  return fgs_get_player(ctx, uuid, conn_baton);
}


fgs_error_t
fgs_update_player (fgs_ctx_t *ctx, fgs_uuid_t player_uuid, char *key,
                   enum fgs_value_type value_type, ...)
{
  fgs_error_t err;
  va_list ap;

  /* Validate parameters */
  if (ctx == NULL || player_uuid == NULL || key == NULL)
    return FGS_ERR_INVALID_PARAMS;

  va_start(ap, value_type);
  err = fgs__proxy_update(ctx, "PLAYERUPDATE", player_uuid, key, value_type, ap);
  va_end(ap);

  return err;
}


fgs_error_t
fgs_commit_player(fgs_ctx_t *ctx, fgs_uuid_t player_uuid)
{
  /* Validate parameters */
  if (ctx == NULL)
    return FGS_ERR_INVALID_PARAMS;

  return fgs__proxy_commit_player(ctx, player_uuid);
}
